package com.example.myapp.render;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import com.example.myapp.activities.MyActivity;
import com.example.myapp.event.ClickOnCellEvent;
import com.example.myapp.event.EventHandler;
import com.example.myapp.models.Game;
import com.example.myapp.views.Line;
import com.example.myapp.views.Triangle;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import java.util.ArrayList;
import java.util.List;

public class CustomRenderer implements GLSurfaceView.Renderer {

    private List<Line> lines = new ArrayList<>();
    private Triangle[] triangles = new Triangle[Game.MATRIX_WIDTH * Game.MATRIX_WIDTH];
    private final List<Runnable> toRend = new ArrayList<>();
    private float ratio;
    private static final int NEAR = 4;

    private final float[] mMVPMatrix = new float[16];
    private final float[] mProjMatrix = new float[16];
    private final float[] mVMatrix = new float[16];
    private final float[] mRotationMatrix = new float[16];

//    // Declare as volatile because we are updating it from another thread
//    public volatile float mAngle;

    public static int loadShader(int type, String shaderCode){

        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(type);

        // add the source code to the shader and compile it
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);

        //draw the board
        float iy = 0.8f;
        float y = iy;
        float ix = -0.8f;
        float x = ix;

        for (int i = 0; i < 9; i++) {
            Line vLine = new Line();
            vLine.SetVerts(ix, y, 0f,
                    -ix, y, 0f);
            vLine.SetColor(.8f, .8f, 0f, 1f);

            Line hLine = new Line();
            hLine.SetVerts(x, iy, 0f, x, -iy, 0f);
            hLine.SetColor(.8f, .8f, 0f, 1f);

            y -= iy / 4;
            x -= ix / 4;

            lines.add(vLine);
            lines.add(hLine);
        }
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);

        ratio = (float) width / height;

        // this projection matrix is applied to object coordinates
        // in the onDrawFrame() method
        //matrix offset left right bottom top near far
        Matrix.frustumM(mProjMatrix, 0,
                -ratio, ratio, -1, 1,
                3, 7);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        synchronized (toRend) {
            for (Runnable runnable : toRend) {
                runnable.run();
            }
            toRend.clear();
        }

        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        // Set the camera position (View matrix)
        //rm, rmoffset; eye x, y, z; center x, y, z; up x, y, z
        Matrix.setLookAtM(mVMatrix, 0,
                0, 0, -NEAR,
                0f, 0f, 0f,
                0f, 1.0f, 0.0f);

        // Calculate the projection and view transformation
        Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);

        for (Line line : lines) {
            line.draw(mMVPMatrix);
        }

        for (int i = 0; i < Game.MATRIX_WIDTH; i++) {
            for (int j = 0; j < Game.MATRIX_WIDTH; j++) {
                if (triangles[i * Game.MATRIX_WIDTH + j] != null) {
                    triangles[i * Game.MATRIX_WIDTH + j].draw(mMVPMatrix);
                }
            }
        }
    }

    /*
     * Event Handler
     */
    private int[] cellTouchTest(float[] position) {
        int x = translateRelativeCoorToIndex(position[0]);
        int y = translateRelativeCoorToIndex(position[1]);
        if (x < 0 || x > 7) {
            x = -1;
        }
        if (y < 0 || y > 7) {
            y = -1;
        }
        return new int[]{x, y};
    }

    private void handleTouchEvent(float upx, float upy, float dnx, float dny) {
        int[] up, down;
        float[] upin, dnin;
        upin = translateToRealWorld(new float[]{upx, upy});
        dnin = translateToRealWorld(new float[]{dnx, dny});
        up = cellTouchTest(upin);
        down = cellTouchTest(dnin);
//        System.out.println("Handling: (" + upx + " " + upy + ")");
//        System.out.println("RealWorld: (" + upin[0] + " " + upin[1] + ")");
//        System.out.println("Translated: (" + up[0] + " " + up[1] + ")");
        if (up[0] == -1 || down[0] == -1 || up[1] == -1 || down[1] == -1) {
            return;
        }
        if (up[0] == down[0] && up[1] == down[1]) {
            //got it
            ClickOnCellEvent e = new ClickOnCellEvent(this, up);
            e.setPart(1);
            EventHandler.handleEvent(e);
        }
    }

    private int selfdown = 0;
    //real coor
    private float lastX, lastY;

    public void handleUpEvent(float x, float y) {
        if (selfdown == 1) {
            handleTouchEvent(x, y, lastX, lastY);
        }
        selfdown = 0;
    }

    public void handleDownEvent(float x, float y) {
        if (selfdown == 0) {
            selfdown = 1;
            lastX = x;
            lastY = y;
        }
    }

    public void handleStateMatrix(int[] stateMatrix) {
        synchronized (toRend) {
            for (int i = 0; i < Game.MATRIX_WIDTH; i++) {
                for (int j = 0; j < Game.MATRIX_WIDTH; j++) {
                    if (stateMatrix[i * Game.MATRIX_WIDTH + j] != -1) {
                        //draw circle at i, j
//                        System.out.println("Should draw a circle at (" + i + " " + j + ")");

                        final int finalJ = j;
                        final int finalI = i;
                        toRend.add(new Runnable() {
                            @Override
                            public void run() {
                                Triangle triangle;
                                triangle = new Triangle();
                                float x = 0.6f - 0.2f * finalI;
                                float y = 0.6f - 0.2f * finalJ;
                                triangle.SetVerts(
                                        x + 0.1f, y + 0.02f, 0f,
                                        x + 0.03f, y + 0.12f, 0f,
                                        x + 0.15f, y + 0.12f, 0f);
                                if (stateMatrix[finalI * Game.MATRIX_WIDTH + finalJ] == 1) {
                                    triangle.SetColor(1f, 0f, 0f, 1f);
                                } else {
                                    triangle.SetColor(0f, 1f, 0f, 1f);
                                }
                                triangles[finalI * Game.MATRIX_WIDTH + finalJ] = triangle;
                            }
                        });
                    } else {
                        triangles[i * Game.MATRIX_WIDTH + j] = null;
                    }
                }
            }
        }
    }

    /*
     * Translator
     */

    /**
     * Translate from [0-1, 0-1] to [-1~1, -1~1] --> [-h~h, -h~h]
     * @param position relative position in screen(0~1)
     * @return translated position in [-1~1] coor
     */
    private float[] translateToRealWorld(float[] position) {
        float h = NEAR / 3f;
        float[] result = new float[2];
        result[0] = h * ratio * (position[0] - 0.5f) / 0.5f;
        result[1] = h * (position[1] - 0.5f) * 2;
        return result;
    }

    private int translateRelativeCoorToIndex(float co) {
        return (int) Math.floor((co + 0.8f) / 0.2f);
    }
}